<?

//require_once(dirname(__FILE__) . '/log.php');

class bf_xml {

	private $opts;
	private $handlers;
	private $tags;
	private $current;

	private $parser;

	protected $log;

	public function __construct($options = array()) {
		$this->opts = array_merge(array(
			'log' => dirname(__FILE__) . '/../../logs/xml-error.log',
		), $options);
		$this->log = new bf_log($this->opts['log']);
	}

	public function current_line() {
		return xml_get_current_line_number($this->parser);
	}

	private function log_error($p) {
		$ts = date('Y-m-d H:i:s');
		$hn = @$_SERVER['SERVER_NAME'];
		$this->log->append("{\n\n$ts $hn\n\n"
			.sprintf("XML Error: %s at line %d", 
				xml_error_string(xml_get_error_code($p)),
				xml_get_current_line_number($p)
			)
			."\n}\n"
		);
	}

	public function set_handlers($h = array()) {
		$this->handlers = $h;
		$this->tags = array_keys($h);
		$this->current = array();
	}

	public function parse_file($filename) {
		$rc = true;
		$this->parser = $p = @xml_parser_create('UTF-8');
		@xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, false);
		@xml_set_element_handler($p, array($this, 'on_start_element'), array($this, 'on_end_element'));
		@xml_set_character_data_handler($p, array($this, 'on_char_data'));
		if ($fp = @fopen($filename, 'r')) {
			@flock($fp, LOCK_SH);		
			while (!@feof($fp)) {
				$data = @fread($fp, 4096);
				if (!@xml_parse($p, $data, feof($fp))) {
					$this->log_error($p);
					$rc = false;
					break;
				}
			}
			@flock($fp, LOCK_UN);
			@fclose($fp);
		}
		@xml_parser_free($p);
		return $rc;
	}

	private function call_handler($h, $e, $evt) {

//echo "{$e['tag']}, $evt\n";

		$e['event'] = $evt;
		$e['lineno'] = $this->current_line();

		if ($h['required-attrs'])
			foreach($h['required-attrs'] as $attr)
				if (!isset($e['attrs'][$attr])) {
					$error = true;
					if (@$h['error-callback'])
						call_user_func($h['error-callback'], $e/*, @$h['error-callback-params']*/); // TODO think
					break;
				}

		if (!$error)
			call_user_func($h['callback'], $e, @$h['callback-params']);
	}

	private function on_start_element($p, $name, $attrs) {
		if (in_array($name, $this->tags)) {
			$e = array('tag' => $name, 'attrs' => $attrs);

			$h = $this->handlers[$e['tag']];
			foreach ($h['default-attrs'] as $name => $value)
				if (!@$e['attrs'][$name])
					$e['attrs'][$name] = $value;

			if (isset($h['event']) && ((is_array($h['event']) && in_array('start', $h['event'])) || ($h['event'] == 'start')))
				$this->call_handler($h, $e, 'start');

			array_push($this->current, $e);
		}
	}

	private function on_end_element($p, $name) {
		if (in_array($name, $this->tags)) {
			$error = false;
			$e = array_pop($this->current);
			$h = $this->handlers[$e['tag']];
			foreach ($h['default-attrs'] as $name => $value)
				if (!@$e['attrs'][$name])
					$e['attrs'][$name] = $value;
			if (!@$e['data'])
				$e['data'] = (@$h['default-data'] ? $h['default-data'] : '');

			if (!isset($h['event']))
				$h['event'] = 'end';
			if (isset($h['event']) && ((is_array($h['event']) && in_array('end', $h['event'])) || ($h['event'] == 'end')))
				$this->call_handler($h, $e, 'end');
		}
	}

	private function on_char_data($p, $data) {
		$this->current[0]['data'] = $data;
	}
}

?>